// --------------------------------------------------------------------------------------------------------------------
// <copyright file="MockProtocol.cs" company="Microsoft Corporation">
// The MIT License (MIT)
// 
// Copyright (c) 2014, Microsoft Corporation
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy
//  of this software and associated documentation files (the "Software"), to deal
//  in the Software without restriction, including without limitation the rights
//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//  copies of the Software, and to permit persons to whom the Software is
//  furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in
//  all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//  THE SOFTWARE.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
namespace Test.Robotics.Hardware.DAC
{
    using System;

    using Microsoft.Robotics.Hardware.DAC;

    /// <summary>
    /// A Mock implementation of IProtocol to be used for testing
    /// </summary>
    public class MockProtocol : IProtocol
    {
        /// <summary>
        /// Minimum packet size
        /// </summary>
        private static int minimumPacketSize = 0;

        /// <summary>
        /// Next sequence number
        /// </summary>
        private ushort nextSeqNo;

        /// <summary>
        /// Initializes a new instance of the <see cref="MockProtocol" /> class.
        /// </summary>
        public MockProtocol()
        {
            this.nextSeqNo = 0;
        }

        /// <summary>
        /// No way to mock this, which might indicate an architectural flaw.  This decodes a message and then calls the passed in
        /// ParseMessage function to handle the internals
        /// </summary>
        /// <param name="eventArgs">EventArgs containing the data buffer to be read</param>
        /// <param name="parse">Function that will handle interpreting the internal portion of the message</param>
        /// <returns>True if message is ill formatted, false if accepted</returns>
        public bool ExtractMessageData(AsyncEventArgs eventArgs, ParseMessage parse)
        {
            bool corrupted = true;
            byte commandId = 0;
            int parametersLength = 0;
            int parametersOffset = 0;
            if (eventArgs.LastError == null)
            {
                byte[] buffer = eventArgs.Buffer;
                int offset = 0;
                int completed = 0;
                int available = eventArgs.Offset + eventArgs.BytesTransferred;
                corrupted = false;
                while (available - offset >= minimumPacketSize)
                {
                    if (BitConverter.ToUInt16(buffer, offset) != Version2Protocol.PacketHeader)
                    {
                        // Skip a byte and continue
                        ++offset;
                        continue;
                    }

                    // Record the start of the message, for any possible crc check
                    int start = offset;

                    // Advance past the header and read the sequence number
                    offset += sizeof(ushort);
                    ushort seqno = BitConverter.ToUInt16(buffer, offset);
                    offset += sizeof(ushort);

                    // Extract the command id and skip the response code
                    commandId = (byte)buffer[offset];
                    offset += sizeof(byte) + sizeof(byte);

                    // Read the length field that indicates the space for params
                    parametersLength = BitConverter.ToUInt16(buffer, offset);
                    offset += sizeof(ushort);

                    // Check we have enough space for the message now we know the length
                    if (available - offset < parametersLength + sizeof(ushort) + sizeof(ushort))
                    {
                        break;
                    }

                    // Skip the params, read the crc and also compute it ourselves
                    parametersOffset = offset;
                    offset += parametersLength;
                    ushort claimedCrc = BitConverter.ToUInt16(buffer, offset);
                    ushort checkedCrc = Crc16.ComputeHash(buffer, start, offset - start);
                    if (claimedCrc != checkedCrc)
                    {
                        corrupted = true;
                        break;
                    }

                    // Check the footer
                    offset += sizeof(ushort);
                    if (BitConverter.ToUInt16(buffer, offset) != Version2Protocol.PacketFooter)
                    {
                        corrupted = true;
                        break;
                    }

                    // Skip past the footer
                    offset += sizeof(ushort);

                    parse(buffer, commandId, seqno, parametersOffset, parametersLength);

                    completed = offset;
                }

                int remaining = available - completed;
                if (remaining > 0)
                {
                    Buffer.BlockCopy(buffer, completed, buffer, 0, remaining);
                    eventArgs.Offset = remaining;
                    eventArgs.Count = buffer.Length - remaining;
                }
                else
                {
                    eventArgs.Offset = 0;
                    eventArgs.Count = buffer.Length;
                }
            }

            return !corrupted;
        }

        /// <summary>
        /// Mocks GetNextSequenceNumber and increments private nextSequenceNumber
        /// </summary>
        /// <returns>The next sequence number</returns>
        public ushort GetNextSequenceNumber()
        {
            return this.nextSeqNo++;
        }

        /// <summary>
        /// Mocks RunCommand
        /// </summary>
        /// <param name="command">The parameter is not used.</param>
        /// <param name="asyncWrite">The parameter is not used.</param>
        public void RunCommand(ICommand command, AsyncEventArgs asyncWrite)
        {
            return;
        }

        /// <summary>
        /// Implements the dispose method
        /// </summary>
        public void Dispose()
        {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Disposes of the class instance
        /// </summary>
        /// <param name="disposing">True if disposing, false if finalizing</param>
        private void Dispose(bool disposing)
        {
        }
    }
}
